/*
 * Noxim - the NoC Simulator
 *
 * (C) 2005-2010 by the University of Catania
 * For the complete list of authors refer to file ../doc/AUTHORS.txt
 * For the license applied to these sources refer to file ../doc/LICENSE.txt
 *
 * This file contains the implementation of the Network-on-Chip
 */

#include "NoximNoC.h"

void NoximNoC::buildMesh()
{
    // Check for routing table availability
    if (NoximGlobalParams::routing_algorithm == ROUTING_TABLE_BASED)
    assert(grtable.load(NoximGlobalParams::routing_table_filename));

    // Check for traffic table availability
    if (NoximGlobalParams::traffic_distribution == TRAFFIC_TABLE_BASED)
    assert(gttable.load(NoximGlobalParams::traffic_table_filename));

    // Create the mesh as a matrix of tiles
    for (int i = 0; i < NoximGlobalParams::mesh_dim_x; i++) {
    for (int j = 0; j < NoximGlobalParams::mesh_dim_y; j++) {
        // Create the single Tile with a proper name
        char tile_name[20];
        sprintf(tile_name, "Tile[%02d][%02d]", i, j);
        t[i][j] = new NoximTile(tile_name);

        // Tell to the router its coordinates
        t[i][j]->r->configure(j * NoximGlobalParams::mesh_dim_x + i,
                  NoximGlobalParams::stats_warm_up_time,
                  NoximGlobalParams::buffer_depth,
                  grtable);

        // Tell to the PE its coordinates
        t[i][j]->pe->local_id = j * NoximGlobalParams::mesh_dim_x + i;
        t[i][j]->pe->traffic_table = &gttable;    // Needed to choose destination
        t[i][j]->pe->never_transmit = (gttable.occurrencesAsSource(t[i][j]->pe->local_id) == 0);

        // Map clock and reset
        t[i][j]->clock(clock);
        t[i][j]->reset(reset);

        /* // disable connection, comment add by Chihhao
        
        // Map Rx signals 
        t[i][j]->req_rx[DIRECTION_NORTH] (req_to_south[i][j]);
        t[i][j]->flit_rx[DIRECTION_NORTH] (flit_to_south[i][j]);
        t[i][j]->ack_rx[DIRECTION_NORTH] (ack_to_north[i][j]);

        t[i][j]->req_rx[DIRECTION_EAST] (req_to_west[i + 1][j]);
        t[i][j]->flit_rx[DIRECTION_EAST] (flit_to_west[i + 1][j]);
        t[i][j]->ack_rx[DIRECTION_EAST] (ack_to_east[i + 1][j]);

        t[i][j]->req_rx[DIRECTION_SOUTH] (req_to_north[i][j + 1]);
        t[i][j]->flit_rx[DIRECTION_SOUTH] (flit_to_north[i][j + 1]);
        t[i][j]->ack_rx[DIRECTION_SOUTH] (ack_to_south[i][j + 1]);

        t[i][j]->req_rx[DIRECTION_WEST] (req_to_east[i][j]);
        t[i][j]->flit_rx[DIRECTION_WEST] (flit_to_east[i][j]);
        t[i][j]->ack_rx[DIRECTION_WEST] (ack_to_west[i][j]);

        // Map Tx signals
        t[i][j]->req_tx[DIRECTION_NORTH] (req_to_north[i][j]);
        t[i][j]->flit_tx[DIRECTION_NORTH] (flit_to_north[i][j]);
        t[i][j]->ack_tx[DIRECTION_NORTH] (ack_to_south[i][j]);

        t[i][j]->req_tx[DIRECTION_EAST] (req_to_east[i + 1][j]);
        t[i][j]->flit_tx[DIRECTION_EAST] (flit_to_east[i + 1][j]);
        t[i][j]->ack_tx[DIRECTION_EAST] (ack_to_west[i + 1][j]);

        t[i][j]->req_tx[DIRECTION_SOUTH] (req_to_south[i][j + 1]);
        t[i][j]->flit_tx[DIRECTION_SOUTH] (flit_to_south[i][j + 1]);
        t[i][j]->ack_tx[DIRECTION_SOUTH] (ack_to_north[i][j + 1]);

        t[i][j]->req_tx[DIRECTION_WEST] (req_to_west[i][j]);
        t[i][j]->flit_tx[DIRECTION_WEST] (flit_to_west[i][j]);
        t[i][j]->ack_tx[DIRECTION_WEST] (ack_to_east[i][j]);
        
        // Map buffer level signals (analogy with req_tx/rx port mapping)
        t[i][j]->free_slots[DIRECTION_NORTH] (free_slots_to_north[i][j]);
        t[i][j]->free_slots[DIRECTION_EAST] (free_slots_to_east[i + 1][j]);
        t[i][j]->free_slots[DIRECTION_SOUTH] (free_slots_to_south[i][j + 1]);
        t[i][j]->free_slots[DIRECTION_WEST] (free_slots_to_west[i][j]);

        t[i][j]->free_slots_neighbor[DIRECTION_NORTH] (free_slots_to_south[i][j]);
        t[i][j]->free_slots_neighbor[DIRECTION_EAST] (free_slots_to_west[i + 1][j]);
        t[i][j]->free_slots_neighbor[DIRECTION_SOUTH] (free_slots_to_north[i][j + 1]);
        t[i][j]->free_slots_neighbor[DIRECTION_WEST] (free_slots_to_east[i][j]);

        // NoP 
        t[i][j]->NoP_data_out[DIRECTION_NORTH] (NoP_data_to_north[i][j]);
        t[i][j]->NoP_data_out[DIRECTION_EAST] (NoP_data_to_east[i + 1][j]);
        t[i][j]->NoP_data_out[DIRECTION_SOUTH] (NoP_data_to_south[i][j + 1]);
        t[i][j]->NoP_data_out[DIRECTION_WEST] (NoP_data_to_west[i][j]);

        t[i][j]->NoP_data_in[DIRECTION_NORTH] (NoP_data_to_south[i][j]);
        t[i][j]->NoP_data_in[DIRECTION_EAST] (NoP_data_to_west[i + 1][j]);
        t[i][j]->NoP_data_in[DIRECTION_SOUTH] (NoP_data_to_north[i][j + 1]);
        t[i][j]->NoP_data_in[DIRECTION_WEST] (NoP_data_to_east[i][j]);

        */
        
    }
    }

    // dummy NoximNoP_data structure
    NoximNoP_data tmp_NoP;

    tmp_NoP.sender_id = NOT_VALID;

    for (int i = 0; i < DIRECTIONS; i++) {
        tmp_NoP.channel_status_neighbor[i].free_slots = NOT_VALID;
        tmp_NoP.channel_status_neighbor[i].available = false;
    }

    // Clear signals for borderline nodes
    for (int i = 0; i <= NoximGlobalParams::mesh_dim_x; i++) {
        req_to_south[i][0].write( 0 );
        ack_to_north[i][0].write( 0 );
        req_to_north[i][NoximGlobalParams::mesh_dim_y].write( 0 );
        ack_to_south[i][NoximGlobalParams::mesh_dim_y].write( 0 );
    
        free_slots_to_south[i][0].write(NOT_VALID);
        free_slots_to_north[i][NoximGlobalParams::mesh_dim_y].write(NOT_VALID);
    
        NoP_data_to_south[i][0].write(tmp_NoP);
        NoP_data_to_north[i][NoximGlobalParams::mesh_dim_y].write(tmp_NoP);
    }
    for (int j = 0; j <= NoximGlobalParams::mesh_dim_y; j++) {
        req_to_east[0][j].write( 0 );
        ack_to_west[0][j].write( 0 );
        req_to_west[NoximGlobalParams::mesh_dim_x][j].write( 0 );
        ack_to_east[NoximGlobalParams::mesh_dim_x][j].write( 0 );
    
        free_slots_to_east[0][j].write(NOT_VALID);
        free_slots_to_west[NoximGlobalParams::mesh_dim_x][j].write(NOT_VALID);
    
        NoP_data_to_east[0][j].write(tmp_NoP);
        NoP_data_to_west[NoximGlobalParams::mesh_dim_x][j].write(tmp_NoP);
    }

    // invalidate reservation table entries for non-exhistent channels
    for (int i = 0; i < NoximGlobalParams::mesh_dim_x; i++) {
        t[i][0]->r->reservation_table.invalidate(DIRECTION_NORTH);
        t[i][NoximGlobalParams::mesh_dim_y - 1]->r->reservation_table.invalidate(DIRECTION_SOUTH);
    }
    for (int j = 0; j < NoximGlobalParams::mesh_dim_y; j++) {
        t[0][j]->r->reservation_table.invalidate(DIRECTION_WEST);
        t[NoximGlobalParams::mesh_dim_x - 1][j]->r->reservation_table.invalidate(DIRECTION_EAST);
    }
}

NoximTile *NoximNoC::searchNode(const int id) const
{
    for (int i = 0; i < NoximGlobalParams::mesh_dim_x; i++)
    for (int j = 0; j < NoximGlobalParams::mesh_dim_y; j++)
        if (t[i][j]->r->local_id == id)
        return t[i][j];

    return false;
}

void NoximNoC::copy_signal_to_tile()
{
    for (int i = 0; i < NoximGlobalParams::mesh_dim_x; i++) {
    for (int j = 0; j < NoximGlobalParams::mesh_dim_y; j++) {

        // Map Rx signals 
        t[i][j]->req_rx[DIRECTION_NORTH].write(req_to_south[i][j].read());
        t[i][j]->req_rx[DIRECTION_EAST ].write(req_to_west [i+1][j].read());
        t[i][j]->req_rx[DIRECTION_SOUTH].write(req_to_north[i][j+1].read());
        t[i][j]->req_rx[DIRECTION_WEST ].write(req_to_east [i][j].read());

        t[i][j]->flit_rx[DIRECTION_NORTH].write(flit_to_south[i][j].read());
        t[i][j]->flit_rx[DIRECTION_EAST ].write(flit_to_west [i+1][j].read());
        t[i][j]->flit_rx[DIRECTION_SOUTH].write(flit_to_north[i][j+1].read());
        t[i][j]->flit_rx[DIRECTION_WEST ].write(flit_to_east [i][j].read());

        // Map Tx signals
        t[i][j]->ack_tx[DIRECTION_NORTH].write(ack_to_south[i][j].read());
        t[i][j]->ack_tx[DIRECTION_EAST ].write(ack_to_west [i+1][j].read());
        t[i][j]->ack_tx[DIRECTION_SOUTH].write(ack_to_north[i][j+1].read());
        t[i][j]->ack_tx[DIRECTION_WEST ].write(ack_to_east [i][j].read());
        
        // Map buffer level signals (analogy with req_tx/rx port mapping)
        t[i][j]->free_slots_neighbor[DIRECTION_NORTH].write(free_slots_to_south[i][j].read());
        t[i][j]->free_slots_neighbor[DIRECTION_EAST ].write(free_slots_to_west [i+1][j].read());
        t[i][j]->free_slots_neighbor[DIRECTION_SOUTH].write(free_slots_to_north[i][j+1].read());
        t[i][j]->free_slots_neighbor[DIRECTION_WEST ].write(free_slots_to_east [i][j].read());

        // NoP 
        t[i][j]->NoP_data_in[DIRECTION_NORTH].write(NoP_data_to_south[i][j].read());
        t[i][j]->NoP_data_in[DIRECTION_EAST ].write(NoP_data_to_west [i+1][j].read());
        t[i][j]->NoP_data_in[DIRECTION_SOUTH].write(NoP_data_to_north[i][j+1].read());
        t[i][j]->NoP_data_in[DIRECTION_WEST ].write(NoP_data_to_east [i][j].read());
    
    }
    }
}

void NoximNoC::run_one_cycle()
{
    int i;int j;
//pe and router process          
#pragma omp parallel for private(i,j)
    for (i = 0; i < NoximGlobalParams::mesh_dim_x; i++)
        for (j = 0; j < NoximGlobalParams::mesh_dim_y; j++){
          t[i][j]->run_one_cycle();
          copy_tile_to_signal();
		  copy_signal_to_tile();
        }
}

void NoximNoC::copy_tile_to_signal()
{
    for (int i = 0; i < NoximGlobalParams::mesh_dim_x; i++) {
    for (int j = 0; j < NoximGlobalParams::mesh_dim_y; j++) {

        // Map Rx signals
        ack_to_north[i][j].write(  t[i][j]->ack_rx[DIRECTION_NORTH].read());
        ack_to_east[i+1][j].write( t[i][j]->ack_rx[DIRECTION_EAST].read());
        ack_to_south[i][j+1].write(t[i][j]->ack_rx[DIRECTION_SOUTH].read());
        ack_to_west[i][j].write(   t[i][j]->ack_rx[DIRECTION_WEST].read());

        // Map Tx signals
        req_to_north[i][j].write(  t[i][j]->req_tx[DIRECTION_NORTH].read());
        req_to_east[i+1][j].write( t[i][j]->req_tx[DIRECTION_EAST].read());
        req_to_south[i][j+1].write(t[i][j]->req_tx[DIRECTION_SOUTH].read());
        req_to_west[i][j].write(   t[i][j]->req_tx[DIRECTION_WEST].read());

        flit_to_north[i][j].write(  t[i][j]->flit_tx[DIRECTION_NORTH].read());
        flit_to_east[i+1][j].write( t[i][j]->flit_tx[DIRECTION_EAST].read());
        flit_to_south[i][j+1].write(t[i][j]->flit_tx[DIRECTION_SOUTH].read());
        flit_to_west[i][j].write(   t[i][j]->flit_tx[DIRECTION_WEST].read());
        
        // Map buffer level signals (analogy with req_tx/rx port mapping)
        free_slots_to_north[i  ][j].write(t[i][j]->free_slots[DIRECTION_NORTH].read());
        free_slots_to_east [i+1][j].write(t[i][j]->free_slots[DIRECTION_EAST ].read());
        free_slots_to_south[i][j+1].write(t[i][j]->free_slots[DIRECTION_SOUTH].read());
        free_slots_to_west [i][j  ].write(t[i][j]->free_slots[DIRECTION_WEST ].read());
        
        // NoP 
        NoP_data_to_north[i  ][j].write(t[i][j]->NoP_data_out[DIRECTION_NORTH].read());
        NoP_data_to_east [i+1][j].write(t[i][j]->NoP_data_out[DIRECTION_EAST ].read());
        NoP_data_to_south[i][j+1].write(t[i][j]->NoP_data_out[DIRECTION_SOUTH].read());
        NoP_data_to_west [i][j  ].write(t[i][j]->NoP_data_out[DIRECTION_WEST ].read());

    }
    }
}
